Объекты доступа к данным (DAO)
==============================

Объекты доступа к данным (DAO) предоставляют общий API для доступа к данным, хранящимся в
различных СУБД. Это позволяет без проблем поменять используемую СУБД на любую другую
без необходимости изменения кода, использующего DAO для доступа к данным.

Yii DAO является надстройкой над [PHP Data Objects (PDO)](http://www.php.net/manual/ru/book.pdo.php) -
расширением, которое предоставляет унифицированный доступ к данным многих популярных
СУБД, таких как MySQL, PostgreSQL. Поэтому для использования Yii DAO необходимо, чтобы
были установлены расширение PDO и соответствующий используемой базе данных драйвер PDO (например, `PDO_MYSQL`).

Yii DAO состоит из четырёх основных классов:

   - [CDbConnection]: представляет подключение к базе данных.
   - [CDbCommand]: представляет запрос к базе данных, который необходимо выполнить.
   - [CDbDataReader]: представляет однонаправленный поток строк данных, возвращаемых в ответ на запрос.
   - [CDbTransaction]: представляет транзакцию базы данных.

Далее мы проиллюстрируем использование Yii DAO на различных примерах.

Соединение с базой данных
-------------------------

Для установления соединения с базой необходимо создать экземпляр класса [CDbConnection] и
активировать его. Дополнительная информация, необходимая для подключения к БД (хост, порт, имя пользователя, пароль и т.д.), указывается
в DSN (Data Source Name). В случае возникновения ошибки в процессе соединения с БД будет выброшено исключение
(например, неверный DSN или неправильные имя пользователя/пароль).

~~~
[php]
$connection=new CDbConnection($dsn,$username,$password);
// устанавливаем соединение
// можно использовать конструкцию try…catch для перехвата возможных исключений
$connection->active=true;
…
$connection->active=false;  // close connection
~~~

Формат DSN зависит от используемого драйвера PDO. Как правило, DSN состоит из имени драйвера PDO,
за которым следует двоеточие, далее указываются параметры подключения, соответствующие синтаксису подключения
используемого драйвера. Подробнее с этим можно ознакомиться в [документации PDO](http://www.php.net/manual/ru/pdo.construct.php).
Ниже представлены несколько основных форматов DSN:

   - SQLite: `sqlite:/path/to/dbfile`
   - MySQL/MariaDB: `mysql:host=localhost;dbname=testdb`
   - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb`
   - SQL Server: `mssql:host=localhost;dbname=testdb`
   - Oracle: `oci:dbname=//localhost:1521/testdb`

Поскольку [CDbConnection] наследует класс [CApplicationComponent], мы можем использовать его
в качестве [компонента приложения](/doc/guide/basics.application#application-component). Для этого нужно настроить
компонент `db` в [конфигурации приложения](/doc/guide/basics.application#application-configuration)
следующим образом:

~~~
[php]
array(
	…
	'components'=>array(
		…
		'db'=>array(
			'class'=>'CDbConnection',
			'connectionString'=>'mysql:host=localhost;dbname=testdb',
			'username'=>'root',
			'password'=>'password',
			'emulatePrepare'=>true,  // необходимо для некоторых версий инсталляций MySQL
		),
	),
)
~~~

Теперь мы можем получить доступ к соединению с БД через `Yii::app()->db`. Чтобы соединение не активировалось
автоматически, необходимо установить значение [CDbConnection::autoConnect] в false.
Этот способ даёт нам возможность использовать одно и то же подключение к БД в любом месте кода.

Выполнение SQL-запросов
------------------------

Когда соединение с БД установлено, мы можем выполнять SQL-запросы, используя [CDbCommand].
Для этого необходимо создать экземпляр класса [CDbCommand] путём вызова [CDbConnection::createCommand()], указав SQL-выражение:

~~~
[php]
$connection=Yii::app()->db; // так можно делать, если в конфигурации настроен компонент соединения "db"
// В противном случае можно создать соединение явно:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// при необходимости SQL-выражение можно изменить:
// $command->text=$newSQL;
~~~

Существуют два способа выполнения SQL-запросов с использованием [CDbCommand]:

   - [execute()|CDbCommand::execute]: выполняет SQL-запросы `INSERT`, `UPDATE` и `DELETE`.
В случае успешного выполнения возвращает количество затронутых строк.

   - [query()|CDbCommand::query]: выполняет SQL-запросы, возвращающие наборы данных, например, запросы `SELECT`.
В случае успешного выполнения возвращает экземпляр класса [CDbDataReader], обеспечивающий доступ к полученным данным.
Для удобства также реализованы методы вида `queryXXX()`, возвращающие результаты напрямую.

Если в процессе выполнения SQL-запроса возникнет ошибка, то будет выброшено исключение.

~~~
[php]
$rowCount=$command->execute();   // выполнение запроса типа `INSERT`, `UPDATE` или `DELETE`
$dataReader=$command->query();   // выполнение запроса типа `SELECT`
$rows=$command->queryAll();      // возвращает все строки результата запроса
$row=$command->queryRow();       // возвращает первую строку результата запроса
$column=$command->queryColumn(); // возвращает первый столбец результата запроса
$value=$command->queryScalar();  // возвращает значение первого поля первой строки результата запроса
~~~

Обработка результатов запроса
-----------------------------

После того как [CDbCommand::query()] создаст экземпляр класса [CDbDataReader], мы можем
получить результат запроса построчно путём повторения вызовов метода [CDbDataReader::read()].
Для получения данных строка за строкой можно также использовать [CDbDataReader] в конструкциях `foreach`.

~~~
[php]
$dataReader=$command->query();
// многократно вызываем read() до возврата методом значения false
while(($row=$dataReader->read())!==false) { … }
// используем foreach для построчного обхода данных
foreach($dataReader as $row) { … }
// получаем все строки разом в виде массива
$rows=$dataReader->readAll();
~~~

> Note|Примечание: Все методы `queryXXX()`, в отличие от [query()|CDbCommand::query], возвращают
данные напрямую. Например, метод [queryRow()|CDbCommand::queryRow] возвращает массив, соответствующий первой
строке результата запроса.

Использование транзакций
------------------------

В случае когда приложение выполняет несколько запросов, каждый из которых что-то пишет или
читает из БД, важно удостовериться, что набор запросов выполнен полностью, а не частично.
В этой ситуации можно воспользоваться транзакциями, представляющими собой экземпляры класса [CDbTransaction]:

   - Начало транзакции.
   - Выполнение запросов по очереди. В этот момент все изменения в базе недоступны извне.
   - Подтверждение транзакции. Если транзакция проведена успешно, то изменения становятся общедоступны.
   - При возникновении ошибки в ходе выполнения запросов происходит откат транзакции в начальное состояние.

Эту последовательность действий можно реализовать следующим образом:

~~~
[php]
$transaction=$connection->beginTransaction();
try
{
	$connection->createCommand($sql1)->execute();
	$connection->createCommand($sql2)->execute();
	//… прочие SQL-запросы
	$transaction->commit();
}
catch(Exception $e) // в случае возникновения ошибки при выполнении одного из запросов выбрасывается исключение
{
	$transaction->rollback();
}
~~~

Привязка параметров
---------------------

Для предотвращения [SQL-инъекций](http://ru.wikipedia.org/wiki/SQL_injection) и
повышения производительности при выполнении однотипных SQL-запросов мы
можем «подготавливать» SQL-выражения, используя маркеры параметров (placeholders), которые в процессе привязки будут
заменяться на реальные значения.

Маркеры параметров могут быть именованными (уникальные маркеры) или неименованными (вопросительные знаки). Для замены маркеров на
реальные значения нужно вызвать методы [CDbCommand::bindParam()] или [CDbCommand::bindValue()].
Экранировать или заключать в кавычки значения параметров не нужно, используемый драйвер базы данных всё сделает сам.
Привязку параметров необходимо осуществить до выполнения SQL-запроса.

~~~
[php]
// выражение SQL с двумя именованными маркерами «:username» и «:email»
$sql="INSERT INTO tbl_user(username, email) VALUES(:username,:email)";
$command=$connection->createCommand($sql);
// заменяем маркер «:username» на соответствующее значение имени пользователя
$command->bindParam(":username",$username,PDO::PARAM_STR);
// заменяем маркер «:email» на соответствующее значение электронной почты
$command->bindParam(":email",$email,PDO::PARAM_STR);
$command->execute();
// вставляем следующую строку с новыми параметрами
$command->bindParam(":username",$username2,PDO::PARAM_STR);
$command->bindParam(":email",$email2,PDO::PARAM_STR);
$command->execute();
~~~

Методы [bindParam()|CDbCommand::bindParam] и [bindValue()|CDbCommand::bindValue] очень похожи.
Единственное различие состоит в том, что первый привязывает параметр к ссылке на переменную PHP,
а второй — к значению. Для параметров, представляющих большой объем данных, с точки зрения производительности
предпочтительнее использовать метод [bindParam()|CDbCommand::bindParam].

Подробнее о привязке параметров можно узнать в
[соответствующей документации PHP](http://php.net/manual/en/pdostatement.bindparam.php).


Привязка полей
----------------

При получении результатов запроса мы также можем привязать поля таблицы к переменным PHP.
Это позволяет автоматически присваивать значения переменным при чтении очередной строки:

~~~
[php]
$sql="SELECT username, email FROM tbl_user";
$dataReader=$connection->createCommand($sql)->query();
// привязываем первое поле (username) к переменной $username
$dataReader->bindColumn(1,$username);
// привязываем второе поле (email) к переменной $email
$dataReader->bindColumn(2,$email);
while($dataReader->read()!==false)
{
    // переменные $username и $email получают значения полей username и email текущей строки
}
~~~

Использование префиксов таблиц
-----------------------------

Yii предоставляет встроенную поддержку префиксов таблиц.
Префикс таблиц — это строка, предваряющая имена таблиц в текущей подключённой БД.
В основном, префиксы используются на виртуальном (shared) хостинге, где к одной БД
подключаются несколько приложений, использующих различные префиксы таблиц в целях избежания конфликтов имён.
Например, одно приложение использует префикс `tbl_`, а другое — `yii_`.

Для использования префикса таблиц установите свойство [CDbConnection::tablePrefix].
Затем в SQL-выражениях используйте `{{TableName}}` для указания имён таблиц, где `TableName` — имя
таблицы без префикса. Например, если БД содержит таблицу `tbl_user`, где `tbl_` — это префикс таблиц,
то мы можем использовать следующий код для получения списка пользователей:

~~~
[php]
$sql='SELECT * FROM {{user}}';
$users=$connection->createCommand($sql)->queryAll();
~~~